package springbook.learningtest.spring.ioc;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.junit.Assert.assertThat;

import org.junit.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import springbook.learningtest.spring.ioc.bean.Hello;
import springbook.learningtest.spring.ioc.bean.Printer;
import springbook.learningtest.spring.ioc.bean.StringPrinter;

public class ApplicationContextTest {
	private String basePath = StringUtils.cleanPath(ClassUtils.classPackageAsResourcePath(getClass())) + "/";
	
	@Test
	public void registerBean() {
		/* StaticApplicaionContext에 의해 설정 메타정보를 등록*/
		StaticApplicationContext ac = new StaticApplicationContext();	//- IoC 컨테이너 생성과 동시에 컨테이너로 동작
		ac.registerSingleton("hello1", Hello.class);	//- Hello 클래스를 hello1이라는 이름의 싱글톤 빈으로 컨테이너에 등록
		
		Hello hello1 = ac.getBean("hello1", Hello.class);	//- IoC 컨테이너가 등록한 빈을 생성했는지 확인하기 위해 빈을 요청하고
		assertThat(hello1, is(notNullValue()));				//- Null이 아닌지 확인
		
		/* BeanDefinition을 이용한 빈 등록 */
		BeanDefinition helloDef = new RootBeanDefinition(Hello.class);	//- 빈 메타정보를 담은 오브젝트를 만든다. 빈 클래스는 Hello로 지정한다.
		helloDef.getPropertyValues().addPropertyValue("name", "Spring");//- 빈의 name 프로퍼티에 들어갈 값을 지정한다.
																		//- <property name="name" value="Spring"/>에 해당한다.
		ac.registerBeanDefinition("hello2", helloDef);	//- 앞에서 생성한 빈 메타정보를 hello2라는 이름을 가진 빈으로 해서
		
		Hello hello2 = ac.getBean("hello2", Hello.class);	//- BeanDefinition으로 등록된 빈이 컨테이너에 의해 만들어지고
		assertThat(hello2.sayHello(), is("Hello Spring"));	//- 프로퍼티 설정이 됐는지 확인한다.
		
		assertThat(hello1, is(not(hello2)));	//- 처음 등록한 빈과 두 번째 등록한 빈이 모두 동일한 Hello 클래스지만
												//- 별개의 오브젝트로 생성됐다.
		assertThat(ac.getBeanFactory().getBeanDefinitionCount(), is(2));
	}
	
	@Test
	public void registerBeanWithDependency() {
		StaticApplicationContext ac = new StaticApplicationContext();
		
		ac.registerBeanDefinition("printer", new RootBeanDefinition(StringPrinter.class));	//- StringPrinter클래스 타입이며,
																							//- printer라는 이름을 가진 빈을 등록한다.
		
		BeanDefinition helloDef = new RootBeanDefinition(Hello.class);
		helloDef.getPropertyValues().addPropertyValue("name", "Spring");	//- 단순 값을 갖는 프로퍼티 등록
		helloDef.getPropertyValues().addPropertyValue("printer", new RuntimeBeanReference("printer"));	//- 아이디가 printer인 빈에 대한 레퍼런스를 프로퍼티로 등록.
		
		ac.registerBeanDefinition("hello", helloDef);
		
		Hello hello = ac.getBean("hello", Hello.class);
		hello.print();
		
		//- Hello 클래스의 print() 메소드는 DI 된 Printer 타입의 오브젝트에게 요청해서 인사말을 출력한다.
		//- 이 결과를 스트링으로 저장해 두는 printer 빈을 통해 확인한다.
		assertThat(ac.getBean("printer").toString(), is("Hello Spring"));
	}
	
	@Test
	public void genericApplicationContext() {
		GenericApplicationContext ac = new GenericApplicationContext();
		XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(ac);
		//- XmlBeanDefinitionReader는 기본적으로 클래스패스로 정의된 리소스로부터 파일을 읽는다.
		reader.loadBeanDefinitions("springbook/learningtest/spring/ioc/genericApplicationContext.xml");
		ac.refresh();//- 모든 메타정보가 등록이 완료됐으니 애플리케이션 컨테이너를 초기화하라는 명령이다.
		
		Hello hello = ac.getBean("hello", Hello.class);
		hello.print();

		assertThat(ac.getBean("printer").toString(), is("Hello Spring"));
	}
	
	@Test
	public void genericXmlApplicationContext() {
		GenericApplicationContext ac = new GenericXmlApplicationContext(basePath + "genericApplicationContext.xml");
		
		Hello hello = ac.getBean("hello", Hello.class);
		hello.print();
		
		assertThat(ac.getBean("printer").toString(), is("Hello Spring"));
		
	}
	
	@Test
	public void contextHierachy() {
		ApplicationContext parent = new GenericXmlApplicationContext(basePath + "parentContext.xml");

		GenericApplicationContext child = new GenericApplicationContext(parent);
		XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(child);
		reader.loadBeanDefinitions(basePath + "childContext.xml");
		child.refresh();
		
		Printer printer = child.getBean("printer", Printer.class);
		assertThat(printer, is(notNullValue()));
		
		Hello hello = child.getBean("hello", Hello.class);
		assertThat(hello, is(notNullValue()));
		
		hello.print();
		assertThat(printer.toString(), is("Hello Child"));
	}
}
